fun is_older ( firstDate : int * int * int, secondDate : int * int * int ) =
    if #1 firstDate < #1 secondDate orelse #1 firstDate > #1 secondDate
    then #1 firstDate < #1 secondDate
    else if #2 firstDate < #2 secondDate orelse #2 firstDate > #2 secondDate
    then #2 firstDate < #2 secondDate
    else #3 firstDate < #3 secondDate

fun number_in_month ( dates : (int * int * int) list, mon : int) =
    if null dates
    then 0
    else  (if #2 (hd dates) = mon
         then 1
	 else 0) + number_in_month ( tl dates, mon )  

fun number_in_months (dates : (int * int * int) list, mons : int list) =
    if null mons
    then 0
    else number_in_month(dates, hd mons) + number_in_months(dates,tl mons)


fun dates_in_month ( dates : (int * int * int) list, mon : int) =
    if null dates 
    then []
    else let val rs = dates_in_month(tl dates, mon)  
         in
	     if #2 (hd dates) = mon
	     then hd dates :: rs
	     else rs
         end

fun dates_in_months ( dates : (int * int * int) list, mons : int list) =
    if null mons 
    then []
    else dates_in_month(dates, hd mons) @ dates_in_months(dates,tl mons)

(* TODO need to check when pos > size(strs) *)
fun get_nth (strs : string list, pos : int ) =
    if pos = 1
    then hd strs
    else get_nth(tl strs, pos-1)

fun date_to_string(date : int * int * int) =
    let val months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
    in
	get_nth(months,#2 date) ^ " " ^ Int.toString(#3 date) ^ ", " ^ Int.toString(#1 date)
    end

fun number_before_reaching_sum (sum : int, ints : int list) =
    if sum - hd ints <= 0 
    then 0
    else 1 + number_before_reaching_sum(sum - hd ints, tl ints)

fun what_month(day_in_year : int) =
    (* add 1 to december since number_before_reaching_sum() requires that sum(all_list_elements) > max value for sum parameter --> exception if sum(all) = 365 so sum(all) = 366 *)
    let val days_in_month = [31,28,31,30,31,30,31,31,30,31,30,32] 
    in
	1 + number_before_reaching_sum(day_in_year,days_in_month)
    end

fun month_range(day1 : int, day2 : int) =
    if day1 > day2
    then []
    else what_month(day1) :: month_range(day1+1,day2) ;

fun oldest(dates : (int * int * int) list) =
    if null dates
    then NONE
    else
	let fun oldest_notempty(dates : (int * int * int) list) =
		if null (tl dates)
		then hd dates
		else
		    let val d = oldest_notempty(tl dates)
                    in
		       if is_older(hd dates,d)
		       then hd dates
		       else d
                    end
	in
	    SOME (oldest_notempty(dates))
	end

(* for personal test only, should be included as a standalone function since is used by both
   the other funcions ? maybe it generates problem with auto-test for grading... *)
(*
fun eliminate_duplicates(ints : int list) =
            (* true if v in ints, false otherwise. false if ints is empty *)
	    let fun contains(ints : int list, v : int) = 
		    (not (null ints)) andalso ((hd ints = v) orelse contains(tl ints, v))
	    in					 
		if null ints
		then []
		else let val rest = eliminate_duplicates(tl ints)
		     in  if contains(rest,hd ints)
			 then rest
			 else (hd ints) :: rest
		     end
	    end
*)

fun number_in_months_challenge(dates : (int * int * int) list, mons : int list) = 
    (* return a list where duplicate elements are eliminated *)
    let fun eliminate_duplicates(ints : int list) =
            (* true if v in ints, false otherwise. false if ints is empty *)
	    let fun contains(ints : int list, v : int) = 
		    (not (null ints)) andalso ((hd ints = v) orelse contains(tl ints, v))
	    in					 
		if null ints
		then []
		else let val rest = eliminate_duplicates(tl ints)
		     in  if contains(rest,hd ints)
			 then rest
			 else (hd ints) :: rest
		     end
	    end
    in
	number_in_months(dates,eliminate_duplicates(mons))
    end

fun dates_in_months_challenge(dates : (int * int * int) list, mons : int list) = 
    (* return a list where duplicate elements are eliminated *)
    let fun eliminate_duplicates(ints : int list) =
            (* true if v in ints, false otherwise. false if ints is empty *)
	    let fun contains(ints : int list, v : int) = 
		    (not (null ints)) andalso ((hd ints = v) orelse contains(tl ints, v))
	    in					 
		if null ints
		then []
		else let val rest = eliminate_duplicates(tl ints)
		     in  if contains(rest,hd ints)
			 then rest
			 else (hd ints) :: rest
		     end
	    end
    in
	dates_in_months(dates,eliminate_duplicates(mons))
    end

fun nth_pos_in_list(list : int list, pos : int) =
    if pos > 1 andalso null (tl list)
    then NONE
    else if pos = 1 
	    then SOME (hd list)
	    else nth_pos_in_list(tl list, pos - 1)

fun reasonable_date(date : int * int * int) =
    let fun nth_pos_in_list(list : int list, pos : int) =
	    if pos = 1 
	    then hd list
            else nth_pos_in_list(tl list, pos - 1);
	fun is_leap_year (year : int) =
	    (year mod 400 = 0) orelse (( year mod 4 = 0 ) andalso not (year mod 100 = 0));
	val days_in_months = if is_leap_year(#1 date)
			     then [31,29,31,30,31,30,31,31,30,31,30,31]
			     else [31,28,31,30,31,30,31,31,30,31,30,31]
    in
	(#1 date) > 0 andalso (#2 date) >= 1 andalso (#2 date) <=12 andalso (#3 date) >= 1 andalso (#3 date) <= nth_pos_in_list(days_in_months,(#2 date)) 
    end

